/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- *//* vim:expandtab:shiftwidth=4:tabstop=4: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"mozilla/ArrayUtils.h"#include"nsArrayUtils.h"#include"nsClipboard.h"#include"HeadlessClipboard.h"#include"nsSupportsPrimitives.h"#include"nsString.h"#include"nsReadableUtils.h"#include"nsXPIDLString.h"#include"nsPrimitiveHelpers.h"#include"nsIServiceManager.h"#include"nsImageToPixbuf.h"#include"nsStringStream.h"#include"nsIObserverService.h"#include"mozilla/Services.h"#include"mozilla/RefPtr.h"#include"mozilla/ClearOnShutdown.h"#include"mozilla/TimeStamp.h"#include"imgIContainer.h"#include<gtk/gtk.h>// For manipulation of the X event queue#include<X11/Xlib.h>#include<gdk/gdkx.h>#include<sys/time.h>#include<sys/types.h>#include<errno.h>#include<unistd.h>#include"X11UndefineNone.h"#include"mozilla/Encoding.h"#include"gfxPlatform.h"usingnamespacemozilla;// Callback when someone asks us for the datavoidclipboard_get_cb(GtkClipboard*aGtkClipboard,GtkSelectionData*aSelectionData,guintinfo,gpointeruser_data);// Callback when someone asks us to clear a clipboardvoidclipboard_clear_cb(GtkClipboard*aGtkClipboard,gpointeruser_data);staticvoidConvertHTMLtoUCS2(guchar*data,int32_tdataLength,char16_t**unicodeData,int32_t&outUnicodeLen);staticvoidGetHTMLCharset(guchar*data,int32_tdataLength,nsCString&str);// Our own versions of gtk_clipboard_wait_for_contents and// gtk_clipboard_wait_for_text, which don't run the event loop while// waiting for the data. This prevents a lot of problems related to// dispatching events at unexpected times.staticGtkSelectionData*wait_for_contents(GtkClipboard*clipboard,GdkAtomtarget);staticgchar*wait_for_text(GtkClipboard*clipboard);staticGdkFilterReturnselection_request_filter(GdkXEvent*gdk_xevent,GdkEvent*event,gpointerdata);namespacemozilla{namespaceclipboard{StaticRefPtr<nsIClipboard>sInstance;}}/* static */already_AddRefed<nsIClipboard>nsClipboard::GetInstance(){usingnamespacemozilla::clipboard;if(!sInstance){if(gfxPlatform::IsHeadless()){sInstance=newwidget::HeadlessClipboard();}else{RefPtr<nsClipboard>clipboard=newnsClipboard();nsresultrv=clipboard->Init();if(NS_FAILED(rv)){returnnullptr;}sInstance=clipboard.forget();}ClearOnShutdown(&sInstance);}RefPtr<nsIClipboard>service=sInstance.get();returnservice.forget();}nsClipboard::nsClipboard(){}nsClipboard::~nsClipboard(){// We have to clear clipboard before gdk_display_close() call.// See bug 531580 for details.if(mGlobalTransferable){gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));}if(mSelectionTransferable){gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));}}NS_IMPL_ISUPPORTS(nsClipboard,nsIClipboard)nsresultnsClipboard::Init(void){nsCOMPtr<nsIObserverService>os=mozilla::services::GetObserverService();if(!os)returnNS_ERROR_FAILURE;os->AddObserver(this,"quit-application",false);// A custom event filter to workaround attempting to dereference a null// selection requestor in GTK3 versions before 3.11.3. See bug 1178799.#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)if(gtk_check_version(3,11,3))gdk_window_add_filter(nullptr,selection_request_filter,nullptr);#endifreturnNS_OK;}NS_IMETHODIMPnsClipboard::Observe(nsISupports*aSubject,constchar*aTopic,constchar16_t*aData){if(strcmp(aTopic,"quit-application")==0){// application is going to quit, save clipboard contentStore();gdk_window_remove_filter(nullptr,selection_request_filter,nullptr);}returnNS_OK;}nsresultnsClipboard::Store(void){// Ask the clipboard manager to store the current clipboard contentif(mGlobalTransferable){GtkClipboard*clipboard=gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);gtk_clipboard_store(clipboard);}returnNS_OK;}NS_IMETHODIMPnsClipboard::SetData(nsITransferable*aTransferable,nsIClipboardOwner*aOwner,int32_taWhichClipboard){// See if we can short cutif((aWhichClipboard==kGlobalClipboard&&aTransferable==mGlobalTransferable.get()&&aOwner==mGlobalOwner.get())||(aWhichClipboard==kSelectionClipboard&&aTransferable==mSelectionTransferable.get()&&aOwner==mSelectionOwner.get())){returnNS_OK;}// Clear out the clipboard in order to set the new dataEmptyClipboard(aWhichClipboard);// List of suported targetsGtkTargetList*list=gtk_target_list_new(nullptr,0);// Get the types of supported flavorsnsCOMPtr<nsIArray>flavors;nsresultrv=aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));if(!flavors||NS_FAILED(rv))returnNS_ERROR_FAILURE;// Add all the flavors to this widget's supported type.boolimagesAdded=false;uint32_tcount;flavors->GetLength(&count);for(uint32_ti=0;i<count;i++){nsCOMPtr<nsISupportsCString>flavor=do_QueryElementAt(flavors,i);if(flavor){nsXPIDLCStringflavorStr;flavor->ToString(getter_Copies(flavorStr));// special case text/unicode since we can handle all of// the string typesif(!strcmp(flavorStr,kUnicodeMime)){gtk_target_list_add(list,gdk_atom_intern("UTF8_STRING",FALSE),0,0);gtk_target_list_add(list,gdk_atom_intern("COMPOUND_TEXT",FALSE),0,0);gtk_target_list_add(list,gdk_atom_intern("TEXT",FALSE),0,0);gtk_target_list_add(list,GDK_SELECTION_TYPE_STRING,0,0);continue;}if(flavorStr.EqualsLiteral(kNativeImageMime)||flavorStr.EqualsLiteral(kPNGImageMime)||flavorStr.EqualsLiteral(kJPEGImageMime)||flavorStr.EqualsLiteral(kJPGImageMime)||flavorStr.EqualsLiteral(kGIFImageMime)){// don't bother adding image targets twiceif(!imagesAdded){// accept any writable image typegtk_target_list_add_image_targets(list,0,TRUE);imagesAdded=true;}continue;}// Add this to our list of valid targetsGdkAtomatom=gdk_atom_intern(flavorStr,FALSE);gtk_target_list_add(list,atom,0,0);}}// Get GTK clipboard (CLIPBOARD or PRIMARY)GtkClipboard*gtkClipboard=gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));gintnumTargets;GtkTargetEntry*gtkTargets=gtk_target_table_new_from_list(list,&numTargets);// Set getcallback and request to store data after an application exitif(gtkTargets&>k_clipboard_set_with_data(gtkClipboard,gtkTargets,numTargets,clipboard_get_cb,clipboard_clear_cb,this)){// We managed to set-up the clipboard so update internal state// We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()// which reset our internal state if(aWhichClipboard==kSelectionClipboard){mSelectionOwner=aOwner;mSelectionTransferable=aTransferable;}else{mGlobalOwner=aOwner;mGlobalTransferable=aTransferable;gtk_clipboard_set_can_store(gtkClipboard,gtkTargets,numTargets);}rv=NS_OK;}else{rv=NS_ERROR_FAILURE;}gtk_target_table_free(gtkTargets,numTargets);gtk_target_list_unref(list);returnrv;}NS_IMETHODIMPnsClipboard::GetData(nsITransferable*aTransferable,int32_taWhichClipboard){if(!aTransferable)returnNS_ERROR_FAILURE;GtkClipboard*clipboard;clipboard=gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));guchar*data=nullptr;gintlength=0;boolfoundData=false;nsAutoCStringfoundFlavor;// Get a list of flavors this transferable can importnsCOMPtr<nsIArray>flavors;nsresultrv;rv=aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));if(!flavors||NS_FAILED(rv))returnNS_ERROR_FAILURE;uint32_tcount;flavors->GetLength(&count);for(uint32_ti=0;i<count;i++){nsCOMPtr<nsISupportsCString>currentFlavor;currentFlavor=do_QueryElementAt(flavors,i);if(currentFlavor){nsXPIDLCStringflavorStr;currentFlavor->ToString(getter_Copies(flavorStr));// Special case text/unicode since we can convert any// string into text/unicodeif(!strcmp(flavorStr,kUnicodeMime)){gchar*new_text=wait_for_text(clipboard);if(new_text){// Convert utf-8 into our unicode format.NS_ConvertUTF8toUTF16ucs2string(new_text);data=(guchar*)ToNewUnicode(ucs2string);length=ucs2string.Length()*2;g_free(new_text);foundData=true;foundFlavor=kUnicodeMime;break;}// If the type was text/unicode and we couldn't get// text off the clipboard, run the next loop// iteration.continue;}// For images, we must wrap the data in an nsIInputStream then return instead of break,// because that code below won't help us.if(!strcmp(flavorStr,kJPEGImageMime)||!strcmp(flavorStr,kJPGImageMime)||!strcmp(flavorStr,kPNGImageMime)||!strcmp(flavorStr,kGIFImageMime)){// Emulate support for image/jpgif(!strcmp(flavorStr,kJPGImageMime)){flavorStr.Assign(kJPEGImageMime);}GdkAtomatom=gdk_atom_intern(flavorStr,FALSE);GtkSelectionData*selectionData=wait_for_contents(clipboard,atom);if(!selectionData)continue;nsCOMPtr<nsIInputStream>byteStream;NS_NewByteInputStream(getter_AddRefs(byteStream),(constchar*)gtk_selection_data_get_data(selectionData),gtk_selection_data_get_length(selectionData),NS_ASSIGNMENT_COPY);aTransferable->SetTransferData(flavorStr,byteStream,sizeof(nsIInputStream*));gtk_selection_data_free(selectionData);returnNS_OK;}// Get the atom for this type and try to request it off// the clipboard.GdkAtomatom=gdk_atom_intern(flavorStr,FALSE);GtkSelectionData*selectionData;selectionData=wait_for_contents(clipboard,atom);if(selectionData){constguchar*clipboardData=gtk_selection_data_get_data(selectionData);length=gtk_selection_data_get_length(selectionData);// Special case text/html since we can convert into UCS2if(!strcmp(flavorStr,kHTMLMime)){char16_t*htmlBody=nullptr;int32_thtmlBodyLen=0;// Convert text/html into our unicode formatConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData),length,&htmlBody,htmlBodyLen);// Try next data format?if(!htmlBodyLen)continue;data=(guchar*)htmlBody;length=htmlBodyLen*2;}else{data=(guchar*)moz_xmalloc(length);if(!data)break;memcpy(data,clipboardData,length);}gtk_selection_data_free(selectionData);foundData=true;foundFlavor=flavorStr;break;}}}if(foundData){nsCOMPtr<nsISupports>wrapper;nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),data,length,getter_AddRefs(wrapper));aTransferable->SetTransferData(foundFlavor.get(),wrapper,length);}if(data)free(data);returnNS_OK;}NS_IMETHODIMPnsClipboard::EmptyClipboard(int32_taWhichClipboard){if(aWhichClipboard==kSelectionClipboard){if(mSelectionOwner){mSelectionOwner->LosingOwnership(mSelectionTransferable);mSelectionOwner=nullptr;}mSelectionTransferable=nullptr;}else{if(mGlobalOwner){mGlobalOwner->LosingOwnership(mGlobalTransferable);mGlobalOwner=nullptr;}mGlobalTransferable=nullptr;}returnNS_OK;}NS_IMETHODIMPnsClipboard::HasDataMatchingFlavors(constchar**aFlavorList,uint32_taLength,int32_taWhichClipboard,bool*_retval){if(!aFlavorList||!_retval)returnNS_ERROR_NULL_POINTER;*_retval=false;GtkSelectionData*selection_data=GetTargets(GetSelectionAtom(aWhichClipboard));if(!selection_data)returnNS_OK;gintn_targets=0;GdkAtom*targets=nullptr;if(!gtk_selection_data_get_targets(selection_data,&targets,&n_targets)||!n_targets)returnNS_OK;// Walk through the provided types and try to match it to a// provided type.for(uint32_ti=0;i<aLength&&!*_retval;i++){// We special case text/unicode here.if(!strcmp(aFlavorList[i],kUnicodeMime)&>k_selection_data_targets_include_text(selection_data)){*_retval=true;break;}for(int32_tj=0;j<n_targets;j++){gchar*atom_name=gdk_atom_name(targets[j]);if(!atom_name)continue;if(!strcmp(atom_name,aFlavorList[i]))*_retval=true;// X clipboard supports image/jpeg, but we want to emulate support// for image/jpg as wellif(!strcmp(aFlavorList[i],kJPGImageMime)&&!strcmp(atom_name,kJPEGImageMime))*_retval=true;g_free(atom_name);if(*_retval)break;}}gtk_selection_data_free(selection_data);g_free(targets);returnNS_OK;}NS_IMETHODIMPnsClipboard::SupportsSelectionClipboard(bool*_retval){*_retval=true;// yeah, unix supports the selection clipboardreturnNS_OK;}NS_IMETHODIMPnsClipboard::SupportsFindClipboard(bool*_retval){*_retval=false;returnNS_OK;}/* static */GdkAtomnsClipboard::GetSelectionAtom(int32_taWhichClipboard){if(aWhichClipboard==kGlobalClipboard)returnGDK_SELECTION_CLIPBOARD;returnGDK_SELECTION_PRIMARY;}/* static */GtkSelectionData*nsClipboard::GetTargets(GdkAtomaWhichClipboard){GtkClipboard*clipboard=gtk_clipboard_get(aWhichClipboard);returnwait_for_contents(clipboard,gdk_atom_intern("TARGETS",FALSE));}nsITransferable*nsClipboard::GetTransferable(int32_taWhichClipboard){nsITransferable*retval;if(aWhichClipboard==kSelectionClipboard)retval=mSelectionTransferable.get();elseretval=mGlobalTransferable.get();returnretval;}voidnsClipboard::SelectionGetEvent(GtkClipboard*aClipboard,GtkSelectionData*aSelectionData){// Someone has asked us to hand them something. The first thing// that we want to do is see if that something includes text. If// it does, try to give it text/unicode after converting it to// utf-8.int32_twhichClipboard;// which clipboard?GdkAtomselection=gtk_selection_data_get_selection(aSelectionData);if(selection==GDK_SELECTION_PRIMARY)whichClipboard=kSelectionClipboard;elseif(selection==GDK_SELECTION_CLIPBOARD)whichClipboard=kGlobalClipboard;elsereturn;// THAT AIN'T NO CLIPBOARD I EVER HEARD OFnsCOMPtr<nsITransferable>trans=GetTransferable(whichClipboard);if(!trans){// We have nothing to serve#ifdef DEBUG_CLIPBOARDprintf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",whichClipboard==kSelectionClipboard?"Selection":"Global");#endifreturn;}nsresultrv;nsCOMPtr<nsISupports>item;uint32_tlen;GdkAtomselectionTarget=gtk_selection_data_get_target(aSelectionData);// Check to see if the selection data includes any of the string// types that we support.if(selectionTarget==gdk_atom_intern("STRING",FALSE)||selectionTarget==gdk_atom_intern("TEXT",FALSE)||selectionTarget==gdk_atom_intern("COMPOUND_TEXT",FALSE)||selectionTarget==gdk_atom_intern("UTF8_STRING",FALSE)){// Try to convert our internal type into a text string. Get// the transferable for this clipboard and try to get the// text/unicode type for it.rv=trans->GetTransferData("text/unicode",getter_AddRefs(item),&len);if(!item||NS_FAILED(rv))return;nsCOMPtr<nsISupportsString>wideString;wideString=do_QueryInterface(item);if(!wideString)return;nsAutoStringucs2string;wideString->GetData(ucs2string);char*utf8string=ToNewUTF8String(ucs2string);if(!utf8string)return;gtk_selection_data_set_text(aSelectionData,utf8string,strlen(utf8string));free(utf8string);return;}// Check to see if the selection data is an image typeif(gtk_targets_include_image(&selectionTarget,1,TRUE)){// Look through our transfer data for the imagestaticconstchar*constimageMimeTypes[]={kNativeImageMime,kPNGImageMime,kJPEGImageMime,kJPGImageMime,kGIFImageMime};nsCOMPtr<nsISupports>imageItem;nsCOMPtr<nsISupportsInterfacePointer>ptrPrimitive;for(uint32_ti=0;!ptrPrimitive&&i<ArrayLength(imageMimeTypes);i++){rv=trans->GetTransferData(imageMimeTypes[i],getter_AddRefs(imageItem),&len);ptrPrimitive=do_QueryInterface(imageItem);}if(!ptrPrimitive)return;nsCOMPtr<nsISupports>primitiveData;ptrPrimitive->GetData(getter_AddRefs(primitiveData));nsCOMPtr<imgIContainer>image(do_QueryInterface(primitiveData));if(!image)// Not getting an image for an image mime type!?return;GdkPixbuf*pixbuf=nsImageToPixbuf::ImageToPixbuf(image);if(!pixbuf)return;gtk_selection_data_set_pixbuf(aSelectionData,pixbuf);g_object_unref(pixbuf);return;}// Try to match up the selection data target to something our// transferable provides.gchar*target_name=gdk_atom_name(selectionTarget);if(!target_name)return;rv=trans->GetTransferData(target_name,getter_AddRefs(item),&len);// nothing found?if(!item||NS_FAILED(rv)){g_free(target_name);return;}void*primitive_data=nullptr;nsPrimitiveHelpers::CreateDataFromPrimitive(target_name,item,&primitive_data,len);if(primitive_data){// Check to see if the selection data is text/htmlif(selectionTarget==gdk_atom_intern(kHTMLMime,FALSE)){/* * "text/html" can be encoded UCS2. It is recommended that * documents transmitted as UCS2 always begin with a ZERO-WIDTH * NON-BREAKING SPACE character (hexadecimal FEFF, also called * Byte Order Mark (BOM)). Adding BOM can help other app to * detect mozilla use UCS2 encoding when copy-paste. */guchar*buffer=(guchar*)moz_xmalloc((len*sizeof(guchar))+sizeof(char16_t));if(!buffer)return;char16_tprefix=0xFEFF;memcpy(buffer,&prefix,sizeof(prefix));memcpy(buffer+sizeof(prefix),primitive_data,len);free((guchar*)primitive_data);primitive_data=(guchar*)buffer;len+=sizeof(prefix);}gtk_selection_data_set(aSelectionData,selectionTarget,8,/* 8 bits in a unit */(constguchar*)primitive_data,len);free(primitive_data);}g_free(target_name);}voidnsClipboard::SelectionClearEvent(GtkClipboard*aGtkClipboard){int32_twhichClipboard;// which clipboard?if(aGtkClipboard==gtk_clipboard_get(GDK_SELECTION_PRIMARY))whichClipboard=kSelectionClipboard;elseif(aGtkClipboard==gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))whichClipboard=kGlobalClipboard;elsereturn;// THAT AIN'T NO CLIPBOARD I EVER HEARD OFEmptyClipboard(whichClipboard);}voidclipboard_get_cb(GtkClipboard*aGtkClipboard,GtkSelectionData*aSelectionData,guintinfo,gpointeruser_data){nsClipboard*aClipboard=static_cast<nsClipboard*>(user_data);aClipboard->SelectionGetEvent(aGtkClipboard,aSelectionData);}voidclipboard_clear_cb(GtkClipboard*aGtkClipboard,gpointeruser_data){nsClipboard*aClipboard=static_cast<nsClipboard*>(user_data);aClipboard->SelectionClearEvent(aGtkClipboard);}/* * when copy-paste, mozilla wants data encoded using UCS2, * other app such as StarOffice use "text/html"(RFC2854). * This function convert data(got from GTK clipboard) * to data mozilla wanted. * * data from GTK clipboard can be 3 forms: * 1. From current mozilla * "text/html", charset = utf-16 * 2. From old version mozilla or mozilla-based app * content("body" only), charset = utf-16 * 3. From other app who use "text/html" when copy-paste * "text/html", has "charset" info * * data : got from GTK clipboard * dataLength: got from GTK clipboard * body : pass to Mozilla * bodyLength: pass to Mozilla */voidConvertHTMLtoUCS2(guchar*data,int32_tdataLength,char16_t**unicodeData,int32_t&outUnicodeLen){nsAutoCStringcharset;GetHTMLCharset(data,dataLength,charset);// get charset of HTMLif(charset.EqualsLiteral("UTF-16")){//current mozillaoutUnicodeLen=(dataLength/2)-1;*unicodeData=reinterpret_cast<char16_t*>(moz_xmalloc((outUnicodeLen+sizeof('\0'))*sizeof(char16_t)));if(*unicodeData){memcpy(*unicodeData,data+sizeof(char16_t),outUnicodeLen*sizeof(char16_t));(*unicodeData)[outUnicodeLen]='\0';}}elseif(charset.EqualsLiteral("UNKNOWN")){outUnicodeLen=0;return;}else{// app which use "text/html" to copy&paste// get the decoderautoencoding=Encoding::ForLabelNoReplacement(charset);if(!encoding){#ifdef DEBUG_CLIPBOARDg_print(" get unicode decoder error\n");#endifoutUnicodeLen=0;return;}autodecoder=encoding->NewDecoder();CheckedInt<size_t>needed=decoder->MaxUTF16BufferLength(dataLength);if(!needed.isValid()||needed.value()>INT32_MAX){outUnicodeLen=0;return;}outUnicodeLen=0;if(needed.value()){*unicodeData=reinterpret_cast<char16_t*>(moz_xmalloc((needed.value()+1)*sizeof(char16_t)));if(*unicodeData){uint32_tresult;size_tread;size_twritten;boolhadErrors;Tie(result,read,written,hadErrors)=decoder->DecodeToUTF16(AsBytes(MakeSpan(data,dataLength)),MakeSpan(*unicodeData,needed.value()),true);MOZ_ASSERT(result==kInputEmpty);MOZ_ASSERT(read==size_t(dataLength));MOZ_ASSERT(written<=needed.value());Unused<<hadErrors;#ifdef DEBUG_CLIPBOARDif(read!=dataLength)printf("didn't consume all the bytes\n");#endifoutUnicodeLen=written;// null terminate.(*unicodeData)[outUnicodeLen]='\0';}}// if valid length}}/* * get "charset" information from clipboard data * return value can be: * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16" * 2. "UNKNOWN": mozilla can't detect what encode it use * 3. other: "text/html" with other charset than utf-16 */voidGetHTMLCharset(guchar*data,int32_tdataLength,nsCString&str){// if detect "FFFE" or "FEFF", assume UTF-16char16_t*beginChar=(char16_t*)data;if((beginChar[0]==0xFFFE)||(beginChar[0]==0xFEFF)){str.AssignLiteral("UTF-16");return;}// no "FFFE" and "FEFF", assume ASCII first to find "charset" infoconstnsDependentCStringhtmlStr((constchar*)data,dataLength);nsACString::const_iteratorstart,end;htmlStr.BeginReading(start);htmlStr.EndReading(end);nsACString::const_iteratorvalueStart(start),valueEnd(start);if(CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("CONTENT=\"text/html;"),start,end)){start=end;htmlStr.EndReading(end);if(CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("charset="),start,end)){valueStart=end;start=end;htmlStr.EndReading(end);if(FindCharInReadable('"',start,end))valueEnd=start;}}// find "charset" in HTMLif(valueStart!=valueEnd){str=Substring(valueStart,valueEnd);ToUpperCase(str);#ifdef DEBUG_CLIPBOARDprintf("Charset of HTML = %s\n",charsetUpperStr.get());#endifreturn;}str.AssignLiteral("UNKNOWN");}staticvoidDispatchSelectionNotifyEvent(GtkWidget*widget,XEvent*xevent){GdkEventevent;event.selection.type=GDK_SELECTION_NOTIFY;event.selection.window=gtk_widget_get_window(widget);event.selection.selection=gdk_x11_xatom_to_atom(xevent->xselection.selection);event.selection.target=gdk_x11_xatom_to_atom(xevent->xselection.target);event.selection.property=gdk_x11_xatom_to_atom(xevent->xselection.property);event.selection.time=xevent->xselection.time;gtk_widget_event(widget,&event);}staticvoidDispatchPropertyNotifyEvent(GtkWidget*widget,XEvent*xevent){GdkWindow*window=gtk_widget_get_window(widget);if((gdk_window_get_events(window))&GDK_PROPERTY_CHANGE_MASK){GdkEventevent;event.property.type=GDK_PROPERTY_NOTIFY;event.property.window=window;event.property.atom=gdk_x11_xatom_to_atom(xevent->xproperty.atom);event.property.time=xevent->xproperty.time;event.property.state=xevent->xproperty.state;gtk_widget_event(widget,&event);}}structcheckEventContext{GtkWidget*cbWidget;AtomselAtom;};staticBoolcheckEventProc(Display*display,XEvent*event,XPointerarg){checkEventContext*context=(checkEventContext*)arg;if(event->xany.type==SelectionNotify||(event->xany.type==PropertyNotify&&event->xproperty.atom==context->selAtom)){GdkWindow*cbWindow=gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),event->xany.window);if(cbWindow){GtkWidget*cbWidget=nullptr;gdk_window_get_user_data(cbWindow,(gpointer*)&cbWidget);if(cbWidget&>K_IS_WIDGET(cbWidget)){context->cbWidget=cbWidget;returnTrue;}}}returnFalse;}// Idle timeout for receiving selection and property notify events (microsec)staticconstintkClipboardTimeout=500000;staticgchar*CopyRetrievedData(constgchar*aData){returng_strdup(aData);}staticGtkSelectionData*CopyRetrievedData(GtkSelectionData*aData){// A negative length indicates that retrieving the data failed.returngtk_selection_data_get_length(aData)>=0?gtk_selection_data_copy(aData):nullptr;}classRetrievalContext{~RetrievalContext(){MOZ_ASSERT(!mData,"Wait() wasn't called");}public:NS_INLINE_DECL_REFCOUNTING(RetrievalContext)enumState{INITIAL,COMPLETED,TIMED_OUT};RetrievalContext():mState(INITIAL),mData(nullptr){}/** * Call this when data has been retrieved. */template<classT>voidComplete(T*aData){if(mState==INITIAL){mState=COMPLETED;mData=CopyRetrievedData(aData);}else{// Already timed outMOZ_ASSERT(mState==TIMED_OUT);}}/** * Spins X event loop until timing out or being completed. Returns * null if we time out, otherwise returns the completed data (passing * ownership to caller). */void*Wait();protected:StatemState;void*mData;};void*RetrievalContext::Wait(){if(mState==COMPLETED){// the request completed synchronouslyvoid*data=mData;mData=nullptr;returndata;}GdkDisplay*gdkDisplay=gdk_display_get_default();if(GDK_IS_X11_DISPLAY(gdkDisplay)){Display*xDisplay=GDK_DISPLAY_XDISPLAY(gdkDisplay);checkEventContextcontext;context.cbWidget=nullptr;context.selAtom=gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",FALSE));// Send X events which are relevant to the ongoing selection retrieval// to the clipboard widget. Wait until either the operation completes, or// we hit our timeout. All other X events remain queued.intselect_result;intcnumber=ConnectionNumber(xDisplay);fd_setselect_set;FD_ZERO(&select_set);FD_SET(cnumber,&select_set);++cnumber;TimeStampstart=TimeStamp::Now();do{XEventxevent;while(XCheckIfEvent(xDisplay,&xevent,checkEventProc,(XPointer)&context)){if(xevent.xany.type==SelectionNotify)DispatchSelectionNotifyEvent(context.cbWidget,&xevent);elseDispatchPropertyNotifyEvent(context.cbWidget,&xevent);if(mState==COMPLETED){void*data=mData;mData=nullptr;returndata;}}TimeStampnow=TimeStamp::Now();structtimevaltv;tv.tv_sec=0;tv.tv_usec=std::max<int32_t>(0,kClipboardTimeout-(now-start).ToMicroseconds());select_result=select(cnumber,&select_set,nullptr,nullptr,&tv);}while(select_result==1||(select_result==-1&&errno==EINTR));}#ifdef DEBUG_CLIPBOARDprintf("exceeded clipboard timeout\n");#endifmState=TIMED_OUT;returnnullptr;}staticvoidclipboard_contents_received(GtkClipboard*clipboard,GtkSelectionData*selection_data,gpointerdata){RetrievalContext*context=static_cast<RetrievalContext*>(data);context->Complete(selection_data);context->Release();}staticGtkSelectionData*wait_for_contents(GtkClipboard*clipboard,GdkAtomtarget){RefPtr<RetrievalContext>context=newRetrievalContext();// Balanced by Release in clipboard_contents_receivedcontext.get()->AddRef();gtk_clipboard_request_contents(clipboard,target,clipboard_contents_received,context.get());returnstatic_cast<GtkSelectionData*>(context->Wait());}staticvoidclipboard_text_received(GtkClipboard*clipboard,constgchar*text,gpointerdata){RetrievalContext*context=static_cast<RetrievalContext*>(data);context->Complete(text);context->Release();}staticgchar*wait_for_text(GtkClipboard*clipboard){RefPtr<RetrievalContext>context=newRetrievalContext();// Balanced by Release in clipboard_text_receivedcontext.get()->AddRef();gtk_clipboard_request_text(clipboard,clipboard_text_received,context.get());returnstatic_cast<gchar*>(context->Wait());}staticGdkFilterReturnselection_request_filter(GdkXEvent*gdk_xevent,GdkEvent*event,gpointerdata){XEvent*xevent=static_cast<XEvent*>(gdk_xevent);if(xevent->xany.type==SelectionRequest){if(xevent->xselectionrequest.requestor==X11None)returnGDK_FILTER_REMOVE;GdkDisplay*display=gdk_x11_lookup_xdisplay(xevent->xselectionrequest.display);if(!display)returnGDK_FILTER_REMOVE;GdkWindow*window=gdk_x11_window_foreign_new_for_display(display,xevent->xselectionrequest.requestor);if(!window)returnGDK_FILTER_REMOVE;g_object_unref(window);}returnGDK_FILTER_CONTINUE;}